package com.jijunpeng.javafx.cipher.controller;

import cn.hutool.crypto.GlobalBouncyCastleProvider;
import cn.hutool.crypto.symmetric.AES;
import com.jijunpeng.javafx.cipher.common.ComboTxtConstants;
import com.jijunpeng.javafx.cipher.common.DialogUtil;
import com.jijunpeng.javafx.cipher.common.enums.AesKeyTypeEnum;
import com.jijunpeng.javafx.cipher.common.enums.CharsetEnum;
import com.jijunpeng.javafx.cipher.common.enums.CipherProviderEnum;
import com.jijunpeng.javafx.cipher.controller.api.BaseController;
import com.jijunpeng.javafx.cipher.controller.api.ICipherController;
import com.jijunpeng.javafx.cipher.exception.KeyTypeException;
import com.jijunpeng.javafx.cipher.persistence.PersistenceJson;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import java.nio.charset.Charset;

/**
 * @author Ji Junpeng
 * @date 2019-11-30 19-45
 */
@Getter
@Slf4j
@PersistenceJson(path = "$.main_inner_$aes")
public class AesController extends BaseController implements ICipherController {
    @FXML
    private Pane aesRootPane;
    @FXML
    @PersistenceJson(path = "leftHexRb", field = "selected", getter = "isSelected")
    private RadioButton leftHexRb;
    @FXML
    @PersistenceJson(path = "leftBase64Rb", field = "selected", getter = "isSelected")
    private RadioButton leftBase64Rb;
    @FXML
    @PersistenceJson(path = "leftStringRb", field = "selected", getter = "isSelected")
    private RadioButton leftStringRb;
    @FXML
    @PersistenceJson(path = "rightHexRb", field = "selected", getter = "isSelected")
    private RadioButton rightHexRb;
    @FXML
    @PersistenceJson(path = "rightBase64Rb", field = "selected", getter = "isSelected")
    private RadioButton rightBase64Rb;
    @FXML
    @PersistenceJson(path = "leftStringCharsetComboBox", field = "value")
    private ComboBox<CharsetEnum> leftStringCharsetComboBox;
    @FXML
    @PersistenceJson(path = "encryptModeComboBox", field = "value")
    private ComboBox<String> encryptModeComboBox;
    @FXML
    @PersistenceJson(path = "paddingComboBox", field = "value")
    private ComboBox<String> paddingComboBox;
    @FXML
    @PersistenceJson(path = "cipherProviderComboBox", field = "value")
    private ComboBox<CipherProviderEnum> cipherProviderComboBox;
    @FXML
    @PersistenceJson(path = "keyTypeComboBox", field = "value")
    private ComboBox<AesKeyTypeEnum> keyTypeComboBox;
    @FXML
    @PersistenceJson(path = "ivTypeComboBox", field = "value")
    private ComboBox<AesKeyTypeEnum> ivTypeComboBox;
    @FXML
    @PersistenceJson(path = "keyStringCharsetComboBox", field = "value")
    private ComboBox<CharsetEnum> keyStringCharsetComboBox;
    @FXML
    @PersistenceJson(path = "ivStringCharsetComboBox", field = "value")
    private ComboBox<CharsetEnum> ivStringCharsetComboBox;
    @FXML
    @PersistenceJson(path = "leftTextArea", field = "text")
    private TextArea leftTextArea;
    @FXML
    @PersistenceJson(path = "rightTextArea", field = "text")
    private TextArea rightTextArea;
    @FXML
    @PersistenceJson(path = "keyTextField", field = "text")
    private TextField keyTextField;
    @FXML
    @PersistenceJson(path = "ivTextField", field = "text")
    private TextField ivTextField;

    @Override
    protected Pane getRootPane() {
        return aesRootPane;
    }

    @Override
    protected void initViewData() {
        super.initViewData();
        initCipherController();
        encryptModeComboBox.setItems(FXCollections.observableArrayList(ComboTxtConstants.LIST_AES_ENCRYPT_MODE));
        encryptModeComboBox.setValue(ComboTxtConstants.LIST_AES_ENCRYPT_MODE[0]);

        paddingComboBox.setItems(FXCollections.observableArrayList(ComboTxtConstants.LIST_AES_PADDING));
        paddingComboBox.setValue(ComboTxtConstants.LIST_AES_PADDING[0]);

        cipherProviderComboBox.setItems(FXCollections.observableArrayList(CipherProviderEnum.values()));
        cipherProviderComboBox.setValue(CipherProviderEnum.values()[0]);

        keyTypeComboBox.setItems(FXCollections.observableArrayList(AesKeyTypeEnum.values()));
        keyTypeComboBox.setValue(AesKeyTypeEnum.values()[0]);

        ivTypeComboBox.setItems(FXCollections.observableArrayList(AesKeyTypeEnum.values()));
        ivTypeComboBox.setValue(AesKeyTypeEnum.values()[0]);

        keyStringCharsetComboBox.setItems(FXCollections.observableArrayList(CharsetEnum.values()));
        keyStringCharsetComboBox.setValue(CharsetEnum.values()[0]);

        ivStringCharsetComboBox.setItems(FXCollections.observableArrayList(CharsetEnum.values()));
        ivStringCharsetComboBox.setValue(CharsetEnum.values()[0]);

        keyTypeComboBox.valueProperty().addListener((observable, oldValue, newValue) -> keyStringCharsetComboBox.setVisible(newValue == AesKeyTypeEnum.STRING));
        ivTypeComboBox.valueProperty().addListener((observable, oldValue, newValue) -> ivStringCharsetComboBox.setVisible(newValue == AesKeyTypeEnum.STRING));
    }

    /**
     * left --encrypt--> right
     */
    @FXML
    private void encrypt(ActionEvent actionEvent) {
        try {
            byte[] inputBytes = getLeftBytes();
            byte[] outputBytes = encrypt(inputBytes);
            String outputStr = buildRightStr(outputBytes);
            rightTextArea.setText(outputStr);
            log.info("AES encrypt. mode:{}, padding:{}, key:{}, iv:{}, inputStr:{}, outputStr:{}",
                    encryptModeComboBox.getValue(), paddingComboBox.getValue(), keyTextField.getText(), ivTextField.getText(), leftTextArea.getText(), outputStr);
        } catch (Exception e) {
            log.error("AES encrypt throw exception. mode:{}, padding:{}, key:{}, iv:{}, inputStr:{}, e:",
                    encryptModeComboBox.getValue(), paddingComboBox.getValue(), keyTextField.getText(), ivTextField.getText(), leftTextArea.getText(), e);
            DialogUtil.showError(aesRootPane, "AES 加密失败", "错误原因：" + e.getMessage());
        }
    }

    @FXML
    private void decrypt(ActionEvent actionEvent) {
        try {
            byte[] inputBytes = getRightBytes();
            byte[] outputBytes = decrypt(inputBytes);
            String outputStr = buildLeftStr(outputBytes);
            leftTextArea.setText(outputStr);
            log.info("AES decrypt. mode:{}, padding:{}, key:{}, iv:{}, inputStr:{}, outputStr:{}",
                    encryptModeComboBox.getValue(), paddingComboBox.getValue(), keyTextField.getText(), ivTextField.getText(), rightTextArea.getText(), outputStr);
        } catch (Exception e) {
            log.error("AES decrypt throw exception. mode:{}, padding:{}, key:{}, iv:{}, inputStr:{}, e:",
                    encryptModeComboBox.getValue(), paddingComboBox.getValue(), keyTextField.getText(), ivTextField.getText(), rightTextArea.getText(), e);
            DialogUtil.showError(aesRootPane, "AES 解密失败", "错误原因：" + e.getMessage());
        }
    }

    private byte[] encrypt(byte[] data) throws DecoderException, KeyTypeException {
        return buildAes().encrypt(data);
    }

    private byte[] decrypt(byte[] data) throws DecoderException, KeyTypeException {
        return buildAes().decrypt(data);
    }

    private AES buildAes() throws DecoderException, KeyTypeException {
        String mode = encryptModeComboBox.getValue();
        String padding = paddingComboBox.getValue();
        GlobalBouncyCastleProvider.setUseBouncyCastle(cipherProviderComboBox.getValue() == CipherProviderEnum.BOUNCY_CASTLE);
        return new AES(mode, padding, getKeyBytes(), getIvBytes());
    }

    private byte[] getKeyBytes() throws KeyTypeException, DecoderException {
        String inputStr = keyTextField.getText();
        byte[] resultBytes;
        switch (keyTypeComboBox.getValue()) {
            case STRING:
                Charset charset = keyStringCharsetComboBox.getValue().getCharset();
                resultBytes = inputStr.getBytes(charset);
                break;
            case BASE64:
                resultBytes = Base64.decodeBase64(inputStr);
                break;
            case HEX:
                resultBytes = Hex.decodeHex(inputStr);
                break;
            default:
                throw new KeyTypeException();
        }
        return resultBytes;
    }

    private byte[] getIvBytes() throws KeyTypeException, DecoderException {
        String inputStr = ivTextField.getText();
        byte[] resultBytes;
        switch (ivTypeComboBox.getValue()) {
            case STRING:
                Charset charset = ivStringCharsetComboBox.getValue().getCharset();
                resultBytes = inputStr.getBytes(charset);
                break;
            case BASE64:
                resultBytes = Base64.decodeBase64(inputStr);
                break;
            case HEX:
                resultBytes = Hex.decodeHex(inputStr);
                break;
            default:
                throw new KeyTypeException();
        }
        return resultBytes;
    }
}
